/**
 * @(#)CodeEntityOverrideAnalyzer.java, 2008-4-30.
 * Copyright 2008  Program Analysis Research Group(PARG),Institute of Software, Peking University.
 * All rights reserved.
 */
package edu.pku.sei.pgie.analyzer.internal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import edu.pku.sei.pgie.analyzer.core.Context;
import edu.pku.sei.pgie.analyzer.core.ICollector;
import edu.pku.sei.pgie.beans.Link;

/**
 * @author HeLi <b>IMPORTANT:</b> very memory consuming, naive algorithm, use
 *         carefully
 */
public class CodeEntityOverrideAnalyzer {
    List<String[]> overWriteRelations;

    HashMap<String, ASTNode> fqnTypeMap;

    Context context;

    ICollector collector;

    public CodeEntityOverrideAnalyzer(Context context) throws Exception {
        this.overWriteRelations = new ArrayList<String[]>();
        this.fqnTypeMap = new HashMap<String, ASTNode>();
        this.context = context;
        this.collector = context.getCollector();
    }

    class MapBuilder extends ASTVisitor {
        public boolean visit(TypeDeclaration node) {
            ITypeBinding typeBinding = node.resolveBinding();
            fqnTypeMap.put(typeBinding.getQualifiedName(), node);
            return true;
        }
    }

    class FukeType {
        public String toString() {
            return "FUKE";
        }

    }

    class OverWriteVisitor extends ASTVisitor {
        Stack superClassStack;

        Stack typeDeclarationStack;

        public OverWriteVisitor() {
            this.superClassStack = new Stack();
            typeDeclarationStack = new Stack();
        }

        public boolean visit(TypeDeclaration node) {
            Type superType = node.getSuperclassType();
            if (superType != null) {
                String fqn = node.getSuperclassType().resolveBinding()
                        .getQualifiedName();
                superClassStack.push(fqn);
            } else {
                superClassStack.push(new FukeType());
            }
            ITypeBinding typeBinding = node.resolveBinding();
            typeDeclarationStack.push(typeBinding.getQualifiedName());
            return true;
        }

        public void endVisit(TypeDeclaration node) {
            if (!superClassStack.isEmpty()) {
                superClassStack.pop();
            }
            if (!typeDeclarationStack.isEmpty()) {
                typeDeclarationStack.pop();
            }
        }

        public boolean visit(AnonymousClassDeclaration node) {
            ITypeBinding typeBinding = node.resolveBinding();
            superClassStack
                    .push(typeBinding.getSuperclass().getQualifiedName());
            return true;
        }

        public void endVisit(AnonymousClassDeclaration node) {
            if (!superClassStack.isEmpty()) {
                superClassStack.pop();
            }
        }

        public boolean visit(MethodDeclaration node) {
            IMethodBinding methodBinding = node.resolveBinding();
            String typeName = (String) typeDeclarationStack.peek();
            // System.out.println(typeName+" "+node.getName().getIdentifier()+"
            // "+superClassStack.peek());
            if (superClassStack.peek() instanceof FukeType) {
                return true;
            }
            String fqn = (String) superClassStack.peek();
            String methodName = node.getName().getIdentifier();
            if (!fqnTypeMap.containsKey(fqn))
                return true;
            TypeDeclaration superType = (TypeDeclaration) fqnTypeMap.get(fqn);
            MethodDeclaration[] methods = superType.getMethods();
            for (int i = 0; i < methods.length; i++) {
                String identifier = methods[i].getName().getIdentifier();
                if (identifier.compareTo(methodName) == 0) {
                    IMethodBinding superSiteBinding = methods[i]
                            .resolveBinding();
                    if (methodBinding.overrides(superSiteBinding)) {
                        overWriteRelations.add(new String[] {
                            typeName, fqn
                        });
                    }
                }
            }
            return true;
        }
    }

    public void analyze(CompilationUnit[] compilationUnits,
            CodeEntitySymbolTable symbolTable) {
        this.overWriteRelations.clear();
        this.fqnTypeMap.clear();
        for (int i = 0; i < compilationUnits.length; i++) {
            compilationUnits[i].accept(new MapBuilder());
        }
        for (int i = 0; i < compilationUnits.length; i++) {
            compilationUnits[i].accept(new OverWriteVisitor());
        }
        for (String[] relation: overWriteRelations) {
            Link link = new Link();
            link.setType("override");
            Long start = symbolTable.getCodeID(relation[0]);
            Long end = symbolTable.getCodeID(relation[1]);
            if (start != null && end != null) {
                link.setStartPoint(start);
                link.setEndPoint(end);
                if (collector != null)
                    collector.collect(link);
            }
        }
    }

    public List getOverwriteRelations() {
        return overWriteRelations;
    }
}
